import fs from 'node:fs/promises'
import {existsSync, readFileSync} from 'node:fs'
import path from 'node:path'
import minimist from 'minimist'
import {gzipSync, brotliCompressSync} from 'node:zlib'
import chalk from 'chalk'
import execa from 'execa'
import {cpus} from 'node:os'
import {createRequire} from 'node:module'
import {targets as allTargets, fuzzyMatchTarget} from './utils.js'
import {scanEnums} from './const-enum.js'

const require = createRequire(import.meta.url)

const args = minimist(process.argv.slice(2))
console.log(process.argv,'--process.argv-');
const targets = args._
const formats = args.formats || args.f
const devOnly = args.devOnly || args.d
const prodOnly = !devOnly && (args.prodOnly || args.p)
const buildTypes = args.withTypes || args.t
const sourceMap = args.sourcemap || args.s
const isRelease = args.release
const buildAllMatching = args.all || args.a
const commit = execa.sync('git', ['rev-parse', 'HEAD']).stdout.slice(0, 7)


run()

async function run() {
    const removeCache = scanEnums()
    try {
        const resolvedTargets = targets.length ? fuzzyMatchTarget(targets, buildAllMatching) : allTargets
        console.log(resolvedTargets);
        await buildAll(resolvedTargets)
        // checkAllSizes(resolvedTargets)
        if (buildTypes) {
            await execa('pnpm', ['run', 'build-dts', ...(targets.length ? ['--environment', `TARGETS:${resolvedTargets.join(',')}`] : [])], {
                stdio: 'inherit'
            })
        }
    } finally {
        removeCache()
    }
}

async function buildAll(targets) {
    console.log(build,'---');
    await runParallel(cpus().length, targets, build)
}

async function runParallel(maxConcurrency, source, iteratorFn) {
    const ret = []
    const executing = []
    for (const item of source) {
        const p = Promise.resolve().then(() => iteratorFn(item, source))
        ret.push(p)

        if (maxConcurrency <= source.length) {
            const e = p.then(() => executing.splice(executing.indexOf(e), 1))
            executing.push(e)
            if (executing.length >= maxConcurrency) {
                await Promise.race(executing)
            }
        }
    }
    return Promise.all(ret)
}

async function build(target) {
    const pkgDir = path.resolve(`packages/${target}`)

    const pkg = require(`${pkgDir}/package.json`)
    console.log(isRelease,'--isRelease--');
    // if this is a full build (no specific targets), ignore private packages
    if ((isRelease || !targets.length) && pkg.private) {
        return
    }

    // if building a specific format, do not remove dist.
    if (!formats && existsSync(`${pkgDir}/dist`)) {
        await fs.rm(`${pkgDir}/dist`, {recursive: true})
    }

    const env = (pkg.buildOptions && pkg.buildOptions.env) || (devOnly ? 'development' : 'production')
    await execa('rollup', ['-c', '--environment', [`COMMIT:${commit}`, `NODE_ENV:${env}`, `TARGET:${target}`, formats ? `FORMATS:${formats}` : ``, prodOnly ? `PROD_ONLY:true` : ``, sourceMap ? `SOURCE_MAP:true` : ``]
        .filter(Boolean)
        .join(',')], {stdio: 'inherit'})
}

function checkAllSizes(targets) {
    if (devOnly || (formats && !formats.includes('global'))) {
        return
    }
    console.log()
    for (const target of targets) {
        checkSize(target)
    }
    console.log()
}

function checkSize(target) {
    const pkgDir = path.resolve(`packages/${target}`)
    checkFileSize(`${pkgDir}/dist/${target}.global.prod.js`)
    if (!formats || formats.includes('global-runtime')) {
        checkFileSize(`${pkgDir}/dist/${target}.runtime.global.prod.js`)
    }
}

function checkFileSize(filePath) {
    if (!existsSync(filePath)) {
        return
    }
    const file = readFileSync(filePath)
    const minSize = (file.length / 1024).toFixed(2) + 'kb'
    const gzipped = gzipSync(file)
    const gzippedSize = (gzipped.length / 1024).toFixed(2) + 'kb'
    const compressed = brotliCompressSync(file)
    // @ts-ignore
    const compressedSize = (compressed.length / 1024).toFixed(2) + 'kb'
    console.log(`${chalk.gray(chalk.bold(path.basename(filePath)))} min:${minSize} / gzip:${gzippedSize} / brotli:${compressedSize}`)
}
